home *** CD-ROM | disk | FTP | other *** search
- /***************************************************************************
- * xsnake - X windows 2D game, make the snake eat the rats.
- * Copyright (C) 1993 Dave K. Redford.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- *
- * Questions about this software should be addressed to:
- *
- * dredf.lax1b@xerox.com (can only handle text)
- *
- ***************************************************************************/
-
- /* xsnake.c - D.Redford - 1/5/91 */
-
- #include <stdio.h>
- #include <ctype.h>
- #include <stdlib.h>
-
- #include <X11/Xlib.h>
- #include <X11/Xatom.h>
- #include <X11/Xutil.h>
-
- static char sccs_id[] = "@(#) xsnake.c v1.4 dkr 93/04/19 20:25:37";
-
- /*---------------------------------------------------------------------------*/
- /* macros */
- #ifndef TRUE
- # define TRUE 1
- #endif /*TRUE*/
- #ifndef FALSE
- # define FALSE 0
- #endif /*FALSE*/
- #define MIN(x,y) ((x<y) ? (x) : (y))
- #define MAX(x,y) ((x>y) ? (x) : (y))
-
- /*---------------------------------------------------------------------------*/
- /* defines */
-
- /* window control */
- /* width vs height aspect ratios */
- #define W_ASPECT (5.0/4.0) /* width is 5/4's height */
- #define H_ASPECT (4.0/5.0) /* height is 4/5's width */
-
- #define TEXT_COLUMNS 52 /* num text cols wanted */
- #define TEXT_LINES 3 /* num lines */
-
- #define MWM_BORDER 14 /* ballpark Mwm title bar width */
-
- /* plot control */
- #define GRID_SIZE 100 /* pseudo screen size */
- /* must a multiple of 10 */
- #define BOX_WIDTH (GRID_SIZE/25) /* size of boxes 1/25th of grid */
- #define BOX_HEIGHT BOX_WIDTH
-
- #define MIN_TAIL (GRID_SIZE/2) /* min tail length */
- #define MAX_TAIL (GRID_SIZE*5) /* max tail length */
- #define DEF_TAIL (GRID_SIZE*2) /* default tail length */
- #define INC_TAIL (GRID_SIZE/2) /* tail length granularity */
- #define NUM_TAIL_INCS (MAX_TAIL / INC_TAIL) /* number of tail lengths */
-
- /* used to access high score table for this tail length */
- #define SCORE_HIGH (high_scores[tail_max / INC_TAIL - 1])
-
- #define BOX_WARNING (GRID_SIZE/4) /* warning lead time for boxes that */
- /* remain - allows you to move 1/4 */
- /* across the grid before remain */
- #define NUM_RAND_BOXES (GRID_SIZE/6) /* ballpark random box figure */
-
- /* game control */
- #define PAUSE_1_SEC (1000) /* 1 second in msecs */
- /* pause value needed to cross grid in 7 seconds */
- #define PAUSE ((PAUSE_1_SEC * 7) / GRID_SIZE)
-
- #define PAUSE_FLASH (PAUSE_1_SEC / 4) /* banner flash rate = 25Hz */
-
- /* game states */
- #define S_INIT 0 /* game init */
- #define S_EXIT 1 /* close down and exit */
- #define S_RUN 2 /* running */
- #define S_SPACE 3 /* wait for space key - after DEAD */
- #define S_CONT 4 /* wait for continue - after space wait */
- #define S_CONFIG 5 /* wait for config type */
- #define S_SET_KEY 6 /* config keys */
- #define S_SET_LEN 7 /* config length */
-
- /* default key values */
- #define KEY_UP 145 /* up */
- #define KEY_DOWN 146 /* down */
- #define KEY_LEFT 144 /* left */
- #define KEY_RIGHT 143 /* right */
-
- #define KEY_SPACE 64 /* sp - continue key */
- #define KEY_CR 35 /* cr - configure key */
- #define KEY_ESC 48 /* esc - exit key */
- #define KEY_L 45 /* l - config length key */
- #define KEY_K 44 /* k - config keys key */
- #define KEY_LESS 58 /* < - length decrease key */
- #define KEY_MORE 59 /* > - length increase key */
-
- /* directions as used by key_list[] and key_dir[] which must match */
- #define KEY_DIR_UP 0 /* up */
- #define KEY_DIR_DOWN 1 /* down */
- #define KEY_DIR_LEFT 2 /* left */
- #define KEY_DIR_RIGHT 3 /* right */
-
-
- /* colour control - must match colour[] and colour_val[] */
- #define COL_WBACK (colour_val[0]) /* window background */
- #define COL_DBACK (colour_val[1]) /* draw area background */
- #define COL_BORDER (colour_val[2]) /* border */
- #define COL_BOX_LIVE (colour_val[3]) /* live box */
- #define COL_BOX_WARN (colour_val[4]) /* warning box */
- #define COL_BOX_DEAD (colour_val[5]) /* dead box */
- #define COL_SNAKE (colour_val[6]) /* snake */
- #define COL_FONT_NORM (colour_val[7]) /* font normal intensity */
- #define COL_FONT_HIGH (colour_val[8]) /* font high */
- #define COL_FONT_CHNG (colour_val[9]) /* font for changing lenght */
-
- #define CONF_FILE ".xsnake.cnf"
-
- /*---------------------------------------------------------------------------*/
- /* globals */
-
- /* window control */
- static Window win; /* game window */
- static Display *dpy; /* main display */
- static Screen *scrn; /* screen */
- static GC gc; /* graphics context */
- static int winW, winH; /* window width and height */
- static int old_winW, old_winH; /* previous for resizing */
-
- static char banner_string[TEXT_COLUMNS];/* current banner string */
-
- static char *config_file; /* pointer to config file name */
-
- /* colours - must match #define COL_*/
- static char *colour[] =
- {
- "midnightblue", /* window background */
- "black", /* draw area background */
- "darkturquoise", /* border */
- "yellow", /* live box */
- "red", /* warning box */
- "maroon", /* dead box */
- "gold", /* snake */
- "lightblue", /* font norm */
- "wheat", /* font high */
- "springgreen" /* font changing length */
- };
-
- #define NUM_COLS (sizeof(colour)/sizeof(char *))
-
- static int colour_val[NUM_COLS];/* values for above */
-
- /* font control */
- static char *font_list[] = /* list of font names to use */
- {
- "*-times-bold-r-normal--8-*",
- "*-times-bold-r-normal--11-*",
- "*-times-bold-r-normal--14-*",
- "*-times-bold-r-normal--17-*",
- "*-times-bold-r-normal--20-*",
- "*-times-bold-r-normal--24-*",
- "*-times-bold-r-normal--25-*",
- };
-
- #define NUM_FONTS (sizeof(font_list)/sizeof(char *))
-
- #define DEF_FONT 2 /* default font rom14 */
-
- static XFontStruct *font_info[NUM_FONTS]; /* font info for each font */
-
- static int
- font_widths[NUM_FONTS], /* all the max widths */
- font_heights[NUM_FONTS], /* all the max heights */
- current_font; /* current selected font */
-
- /* plot control */
- static int drawx,draww, /* drawing area x-pos and width */
- drawy,drawh, /* y-pos and height */
- pointw,pointh, /* point size for scaling */
- pset_inhibit, /* flag for pset */
- redraw_skip; /* flag for redrawing - when resizing */
-
- static float scalex, /* x and y scaling to map grid to */
- scaley; /* drawing area */
-
- static int
- grid[GRID_SIZE][GRID_SIZE], /* pseudo screen - plotting area */
- current_foreground, /* current foreground colour */
- col_max_len; /* current max length colour */
-
- /* event flags */
- static int obscured, /* true when window is obscured */
- suspend; /* true when game should stop - due to */
- /* obscured window or mouse out of window */
-
- /* game control */
- static int ring[MAX_TAIL][2]; /* tail x,y history */
-
- static int
- last_key_hit, /* last key press */
- game_state; /* current game state */
-
- static int key_list[] = /* direction keys - set to defaults */
- { /* must match KEY_DIR_xxxxx */
- KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT
- };
-
- static char *key_dir[] = /* direction strings */
- { /* must match KEY_DIR_xxxxx */
- "UP", "DOWN", "LEFT", "RIGHT"
- };
-
- static int
- head_ring, /* head ring postion */
- tail_ring, /* tail ring postion */
- head_x, /* head x coord */
- head_y, /* head y coord */
- xinc, /* x increment - for direction */
- yinc, /* y increment - for direction */
-
- /* NB values and delays are all in grid positions */
- box_delay, /* delay before new box should appear */
- box_value, /* value of box when hit */
- box_remain, /* box will remain flag */
- box_life, /* time box will stay alive */
- box_x_pos, /* live box x pos */
- box_y_pos, /* live box y pos */
-
- tail_delay, /* tail grow amount - decrements as tail grows */
- tail_max, /* max tail length */
- snake_len, /* current snake length */
- score, /* current score */
- score_last, /* last score */
- high_scores[NUM_TAIL_INCS]; /* high score table 1 for each max length */
-
- /*---------------------------------------------------------------------------*/
- static char *help_text[] =
- {
- "Your task is to move the snake around the screen and try to hit the yellow",
- "or red boxes.",
- "",
- "When you hit a box your score will increase, the box will disappear and your",
- "tail will grow longer. If the maximum tail length is exceeded your tail will",
- "snap off leaving a dead tail.",
- "",
- "A yellow box will appear randomly and remain for a random time, if you ",
- "don't hit it in time it will either disappear or die (maroon colour).",
- "",
- "If a box is going to die it will turn red to warn you, if you hit it during",
- "this time you will gain ten points.",
- "Conversely, if you allow a box to disappear you will lose ten points.",
- "",
- "If you hit anything other than a yellow or red box (including your own tail,",
- "dead tails, the walls or dead boxes) the game will end.",
- "",
- "The default direction keys are:- q Up. , Left.",
- " a Down. . Right.",
- "",
- "The tail length and direction keys can be changed within the game by entering",
- "configuration mode at the appropriate prompt.",
- "",
- "The length can also be changed at game start with the -l option.",
- "",
- "The window size can be changed by the mouse or by selecting a scale at game",
- "start with the -s option.",
- "",
- "The keys, high scores, maximum tail length, and window size are recorded",
- "on file so they can be restored if you play again.",
- "",
- "There is a different high score recorded for each maximum tail length value.",
- "",
- "The game will pause if the mouse is outside the game window or if the window",
- "is obscured.",
- "",
- "At any time the game can be exited by pressing the escape key.",
- "",
- 0x00
- };
-
- /*---------------------------------------------------------------------------*/
- /* set keys, font and max tail to defaults, and set high scores to zero */
- static void set_defaults()
- {
- int i;
-
- current_font = DEF_FONT; /* set the defaults */
- tail_max = DEF_TAIL;
-
- for (i=0; i<NUM_TAIL_INCS; i++) /* for all high scores */
- high_scores[i] = 0;
-
- key_list[0] = KEY_UP; key_list[1] = KEY_DOWN;
- key_list[2] = KEY_LEFT; key_list[3] = KEY_RIGHT;
- }
-
- /*---------------------------------------------------------------------------*/
- /* read the four direction keys, max tail length, font No. and the high score
- * table from the file - if the read fails then set them to default values
- * but keep the filename for writing to later */
- static void read_file(fp)
- FILE *fp;
- {
- int i, fail = TRUE;
-
- if (fscanf ( /* keys and tail */
- fp, "%d %d %d %d %d %d",
- &key_list[0], &key_list[1], &key_list[2],
- &key_list[3], &tail_max, ¤t_font) == 6)
- {
- fail = FALSE;
- for (i=0; i<NUM_TAIL_INCS; i++) /* high score table */
- {
- if (fscanf(fp, "%d", &high_scores[i]) != 1)
- {
- fail = TRUE;
- break;
- }
- }
- }
-
- if (fclose(fp) != 0) /* finished with file */
- {
- perror("Can't close file");
- exit(1);
- }
-
- if (fail == TRUE) /* read failed */
- set_defaults();
- }
-
- /*---------------------------------------------------------------------------*/
- /* scan the PATH looking for the configuration file. The path may not contain
- * the users home directory so add it. If the file is found record the name
- * for writing to later.
- * If the path is duff or the file isn't found set the default values for
- * tail length, font, keys and high scores. */
- static void find_file()
- {
- char *p1, *path, *nextpath, *testfile, *name;
- FILE *fp;
- int length, namelen;
-
- config_file = NULL; /* use this as a flag */
- length = strlen(CONF_FILE);
-
- if ((p1 = getenv("PATH")) != NULL) /* if a path variable set */
- {
- /* get login name & length */
- namelen = (name = getlogin()) == NULL ? 0 : strlen(name);
-
- /* grab space for path */
- if ((path = malloc(strlen(p1) + namelen + 6)) != NULL)
- {
- if (namelen >0) /* if got name add home dir */
- sprintf(path, "/u/%s:%s", name, p1);
- else
- strcpy(path, p1); /* else just use PATH */
-
- nextpath = strtok(path, ":"); /* for all directories */
- while(nextpath != NULL)
- { /* grab space to build file name */
- if ((testfile = malloc(strlen(nextpath) + length + 4)) != NULL)
-
- { /* create name */
- sprintf(testfile, "%s/%s", nextpath, CONF_FILE);
-
- if ((fp = fopen(testfile, "r")) != NULL)
- { /* it opened */
- config_file = testfile; /* record file name */
- break;
- }
- }
- else
- {
- printf("find file/1 malloc failed\n");/* oh dear */
- exit(1);
- }
- free(testfile); /* didn't open */
- nextpath = strtok(NULL,":"); /* try next directory */
- }
- free(path); /* free this */
- }
- else
- {
- printf("find file/2 malloc failed\n"); /* oh dear */
- exit(1);
- }
- } /* no PATH variable */
-
- if (config_file != NULL) /* found a file */
- read_file(fp); /* try and read it */
- else
- set_defaults(); /* no file */
- }
-
- /*---------------------------------------------------------------------------*/
- /* save the four directions keys, the max tail length, the current font No.,
- * and the high score table in the config file. If the config file wasn't
- * opened for read succesfully at prog start then create one in the users
- * home directory. if the write fails just forget it. */
- static void file_save()
- {
- FILE *fp = NULL;
- int fail, i;
- char *ptr, *name;
-
- if (config_file == NULL) /* if file wasn't found or was duff */
- {
- if ((name = getlogin()) == NULL) /* get user name */
- return; /* forget it */
-
- if ((ptr = malloc(strlen(CONF_FILE) + strlen(name) + 1)) != NULL)
- {
- /* create file name */
- sprintf(ptr, "/u/%s/%s", name, CONF_FILE);
-
- if ((fp = fopen(ptr, "wc")) != NULL) /* create file */
- config_file = ptr; /* yes - use it */
- }
- else
- {
- printf("file save malloc failed\n");
- exit(1);
- }
- }
- else /* file was read at startup */
- fp = fopen(config_file, "w"); /* open it */
-
- if (fp != NULL) /* if got a file to use */
- {
- fail = TRUE; /* write keys and tail */
- if (fprintf(fp, "%d %d %d %d %d %d ",
- key_list[0], key_list[1], key_list[2],
- key_list[3], tail_max, current_font) > 0)
- {
- fail = FALSE;
- for (i=0; i<NUM_TAIL_INCS; i++) /* write high score table */
- {
- if (fprintf(fp, "%d ", high_scores[i]) < 0)
- {
- fail = TRUE;
- break;
- }
- }
- }
-
- if (fclose(fp) != 0) /* done with it */
- {
- perror("Can't close file");
- exit(1);
- }
- }
- }
-
- /*---------------------------------------------------------------------------*/
- /* seed random num generator to the current time */
- seed_rnd()
- {
- long ltime;
-
- time (<ime); /* get seconds */
- srand48 (ltime % 32767); /* reduce and pass */
- }
-
- /*---------------------------------------------------------------------------*/
- /* returns rnd num between x and y */
- static int get_rnd(x,y)
- int x,y;
- {
- int mod;
-
- mod = y - x + 1; /* get number of values required */
- return (lrand48() % mod + x); /* get rnd num + add in range */
- }
-
- /*---------------------------------------------------------------------------*/
- /* set foreground colour - if not already set to that colour */
- static void foreground(col)
- int col;
- {
- if (current_foreground != col) /* is it same */
- {
- current_foreground = col; /* no - set it */
- XSetForeground(dpy, gc, col);
- }
- }
-
- /*---------------------------------------------------------------------------*/
- /* print string and number at col,line - in current colour */
- static void disp_string_num(str, num, col,line)
- char *str;
- int num,col,line;
- {
- char *s1[TEXT_COLUMNS +2];
-
- sprintf(s1, "%s% .4d", str, num); /* blank before dot is for sign */
-
- XDrawImageString(dpy, win, gc,
- font_widths[current_font] * col, font_heights[current_font] * line,
- s1, strlen(s1));
- XSync(dpy,0);
- }
-
- /*---------------------------------------------------------------------------*/
- /* print score at col 1 line 1 */
- static void update_score()
- {
- foreground(COL_FONT_NORM);
- disp_string_num("Score :", score, 1,1);
- }
-
- /*---------------------------------------------------------------------------*/
- /* print length at col 1 line 2 */
- static void update_length()
- {
- foreground(COL_FONT_NORM);
- disp_string_num("Length:", snake_len, 1,2);
- }
-
- /*---------------------------------------------------------------------------*/
- /* print last score at col 16 line 1 */
- static void update_last()
- {
- foreground(COL_FONT_NORM);
- disp_string_num("Last Score:", score_last, 16,1);
- }
-
- /*---------------------------------------------------------------------------*/
- /* print high score at col 35 line 1 */
- static void update_high()
- {
- foreground(COL_FONT_NORM);
- disp_string_num("High Score:", SCORE_HIGH, 35,1);
- }
-
- /*---------------------------------------------------------------------------*/
- /* print max length at col 35 line 2 */
- static void update_max()
- {
- foreground(col_max_len);
- disp_string_num("Max Length:", tail_max, 35,2);
- }
-
- /*---------------------------------------------------------------------------*/
- /* print string in middle of line 3 - in highlight colour */
- static void banner_prim(s1)
- char *s1;
- {
- int x;
-
- foreground(COL_FONT_HIGH);
- /* centre the text */
- x = (winW - XTextWidth(font_info[current_font], s1, strlen(s1)))/2;
- XDrawImageString(dpy, win, gc, x, font_heights[current_font] * 3,
- s1, strlen(s1));
- XSync(dpy,0);
- }
-
- /*---------------------------------------------------------------------------*/
- /* blank the banner line */
- static void banner_blank()
- {
- char *blank;
- int i;
- /* grab some string space */
- if ((blank = malloc(TEXT_COLUMNS)) != NULL)
- {
- for (i=0; i<TEXT_COLUMNS; i++) /* make it spaces */
- blank[i] = ' ';
-
- blank[TEXT_COLUMNS -1] = '\0'; /* terminate */
-
- banner_prim(blank); /* overwrite old with blank */
- free(blank); /* free off */
- }
- else
- {
- printf("banner blank malloc failed\n");
- exit(1);
- }
- }
-
- /*---------------------------------------------------------------------------*/
- /* print string on banner line - erasing the previous string - if any */
- static void banner(s1)
- char *s1;
- {
- if (strlen(s1) >= TEXT_COLUMNS) /* will it fit */
- {
- printf("banner string '%s' too long\n", s1);
- exit(1);
- }
- strcpy(banner_string, s1); /* save banner string - for redraws */
-
- banner_blank(); /* blank the line */
- banner_prim(s1); /* now draw the string */
- }
-
- /*---------------------------------------------------------------------------*/
- /* flash the banner string */
- static void flash_banner()
- {
- int i;
-
- for (i=0; i<3; i++) /* flash 3 times */
- {
- nap(PAUSE_FLASH); /* wait */
- banner_blank(); /* blank it */
- nap(PAUSE_FLASH); /* wait */
- banner(banner_string); /* redraw it */
- }
- sleep(1); /* show it for a 1 second */
- }
-
- /*---------------------------------------------------------------------------*/
- /* map a grid coord - to the drawing area (assumes grid is the smaller).
- *
- * NB. If scale = 2.4, then 99 * 2.4 = 237.6 and 100 * 2.4 = 240 thus 2.4
- * points on the draw area are missed.
- * To correct this round down the x,y coords by the scale/2 and round up the
- * width and height by scale/2. This fills in the gap correctly if the grid
- * is 100 and the window size is >= 170 pixels.
- *
- * Unfortunately - for the sizes of window we are using with fonts from rom8
- * to rom29 the best rounding values have to be found by trial and error.
- * They seem to be 3.2 for x coords and 4 for the y - this prevents too much
- * overlap spoiling the accuracy of plotting.
- *
- * For speed the point width and height are set in set_scales().
- */
- static void pscale(x,y, px,py)
- int x,y, *px,*py;
- {
- float fx,fy;
-
- fx = x * scalex + drawx; /* map x coord to scale & add draw x offset */
- fy = y * scaley + drawy; /* map y coord to scale & add draw y offset */
-
- *px = (int)(fx - scalex/3.2); /* round the new points down */
- *py = (int)(fy - scaley/4.0);
- }
-
- /*---------------------------------------------------------------------------*/
- /* set the point in the given colour - draws on screen and maps it into the
- * grid */
- static void pset(x,y,col)
- int x,y,col;
- {
- int px,py,pw,ph;
-
- grid[x][y] = col; /* set grid used */
-
- if (pset_inhibit) /* if shouldn't draw exit */
- return; /* see set_random_boxes() */
-
- pscale(x,y, &px,&py); /* scale the coord for drawing */
-
- foreground(col); /* set colour */
- XFillRectangle(dpy,win,gc, px, py, pointw, pointh);/* draw it */
- XSync(dpy,0);
- }
-
- /*---------------------------------------------------------------------------*/
- /* if grid coord is not BLANK then return true */
- static int ptest(x,y)
- int x,y;
- {
- return (grid[x][y] != COL_DBACK);
- }
-
- /*---------------------------------------------------------------------------*/
- /* set the grid scale values so that we get a best fit for a 4/5 ratio
- * rectangle in the window size provided */
- static void set_scales()
- {
- int width, height, text_height;
-
- text_height = font_heights[current_font] * TEXT_LINES + MWM_BORDER;
-
- height = winH - text_height; /* real height available */
- width = winW; /* real width */
-
- if (width * H_ASPECT > height) /* can we use width */
- {
- if (height * W_ASPECT > width) /* can we use height */
- {
- printf("SCALE Failed\n"); /* both failed */
- exit(1); /* shouldn't happen */
- }
- else
- width = height * W_ASPECT; /* use height */
- }
- else
- height = width * H_ASPECT; /* use width */
-
- /* set draw bounds info */
- drawx = 0; /* x start */
- draww = width; /* width */
- drawy = text_height; /* y start */
- drawh = height; /* height */
-
- /* suss scaling to our grid size */
- scalex = draww / (float)GRID_SIZE;
- scaley = drawh / (float)GRID_SIZE;
-
- /* set point size - see pscale() */
- pointw = (int)(scalex + scalex/3.2);
- pointh = (int)(scaley + scaley/4.0);
- }
-
- /*---------------------------------------------------------------------------*/
- /* if the window size has changed choose a font that best fits the new
- * width using TEXT_COLUMNS for number of chars required.
- */
- static void choose_font()
- {
- int i, height;
-
- if (winW != old_winW || winH != old_winH)
- {
- redraw_skip = 1; /* since a resize will generate a new set of */
- /* config and expose events - skip the first redraw */
- /* and use the last one - see redraw_screen() */
- current_font = -1;
-
- for (i=0; i<NUM_FONTS; i++) /* from smallest font to largest */
- { /* if it fits */
- if (font_widths[i] * TEXT_COLUMNS <= winW)
- current_font = i; /* use it */
- }
- if (current_font == -1) /* if didn't get a fit */
- current_font = 0; /* use smallest */
-
- /* use new font */
- XSetFont(dpy, gc, font_info[current_font]->fid);
-
- /* set dimensions */
- winW = font_widths[current_font] * TEXT_COLUMNS;
- winH = winW * H_ASPECT +
- font_heights[current_font] * TEXT_LINES + MWM_BORDER;
-
- old_winW = winW; /* record them */
- old_winH = winH;
- XResizeWindow(dpy, win, winW, winH); /* resize window */
- file_save(); /* save font */
- }
- }
-
- /*---------------------------------------------------------------------------*/
- /* set the game border around the edge of the grid - allow a redraw to
- * display it */
- set_border()
- {
- int i;
-
- for (i=0; i<GRID_SIZE; i++)
- {
- grid[i][0] = COL_BORDER; /* top line */
- grid[i][GRID_SIZE-1] = COL_BORDER; /* bottom */
-
- grid[0][i] = COL_BORDER; /* left */
- grid[GRID_SIZE-1][i] = COL_BORDER; /* right */
- }
- }
-
- /*---------------------------------------------------------------------------*/
- /* load all the fonts and set the height and width information */
- static void load_fonts()
- {
- int i;
-
- for (i=0; i<NUM_FONTS; i++) /* for all fonts */
- {
- if ((font_info[i] = XLoadQueryFont(dpy, font_list[i])) == NULL)
- {
- printf("Can't load font %s\n", font_list[i]);
- exit(1);
- }
- font_heights[i] =
- font_info[i]->max_bounds.ascent + font_info[i]->max_bounds.descent;
-
- font_widths[i] =
- font_info[i]->max_bounds.rbearing - font_info[i]->min_bounds.lbearing;
- }
- }
-
- /*---------------------------------------------------------------------------*/
- /* create a window and set background to black - also set relevant event
- * masks, colours and load fonts */
- static void create_window()
- {
- char *display = NULL;
- XEvent event;
- XSetWindowAttributes attrib;
- int winX, winY, i;
- XColor xcolour, screencolour;
- Colormap default_cmap;
-
- /* set display */
- if(!(dpy = XOpenDisplay(display)))
- {
- perror("Cannot open display\n");
- exit(1);
- }
-
- /* load colours */
- scrn = DefaultScreenOfDisplay(dpy);
- default_cmap = DefaultColormap(dpy, DefaultScreen(dpy));
-
- for (i=0; i<NUM_COLS; i++)
- {
- if (!XAllocNamedColor
- (dpy, default_cmap, colour[i], &screencolour, &xcolour))
- {
- printf("Colour %s not found\n", colour[i]);
- exit(1);
- }
- colour_val[i] = xcolour.pixel;
- }
-
- /* load fonts - and set window dimensions - to font requirements */
- load_fonts();
- winW = old_winW = font_widths[current_font] * TEXT_COLUMNS;
- winH = old_winH = winW * H_ASPECT +
- font_heights[current_font] * TEXT_LINES + MWM_BORDER;
-
- winX = (WidthOfScreen(scrn) - winW) / 2; /* centre window on screen */
- winY = (HeightOfScreen(scrn) - winH) / 2;
-
- /* set up event masks, window background col & create window */
- attrib.event_mask =
- ( StructureNotifyMask | VisibilityChangeMask | StructureNotifyMask |
- KeyPressMask | EnterWindowMask | LeaveWindowMask |
- ExposureMask);
-
- attrib.background_pixel = COL_WBACK;
-
- win = XCreateWindow(dpy, RootWindowOfScreen(scrn), winX, winY, winW, winH,
- 0, DefaultDepthOfScreen(scrn), InputOutput,
- DefaultVisualOfScreen(scrn),
- CWEventMask | CWBackPixel, &attrib);
-
- /* change title bar */
- XChangeProperty(dpy, win, XA_WM_NAME, XA_STRING, 8,
- PropModeReplace, "Snake",5);
- XMapWindow(dpy, win);
- XFlush(dpy);
- XSync(dpy,0);
-
- /* Set up a graphics context: */
- gc = XCreateGC(dpy, win, 0, NULL);
- XSetFunction(dpy, gc, GXcopy);
-
- /* set font */
- XSetFont(dpy, gc, font_info[current_font]->fid);
-
- XFlush(dpy);
- XSync(dpy,0);
-
- XSetBackground(dpy,gc,COL_WBACK);
- XClearArea(dpy,win,0,0,winW,winH,1);
- XFlush(dpy);
-
- for(i=0;i<100;i++)
- XSync(dpy,0);
- }
-
- /*---------------------------------------------------------------------------*/
- /* trash any accumulated events */
- static void flush_events()
- {
- XEvent report;
-
- while(XPending(dpy)) /* while events */
- XNextEvent(dpy,&report); /* get event */
- }
-
- /*---------------------------------------------------------------------------*/
- /* set font based on window size, set scaling, update text and scan the grid
- * redrawing all points that are not the BLANK colour */
- static void redraw_screen()
- {
- int i,j;
-
- if (redraw_skip-- >0) /* if just done a resize - don't redraw */
- return; /* yet - another expose will be along later */
-
- update_score(); /* update all text */
- update_length();
- update_last();
- update_high();
- update_max();
- banner(banner_string);
-
- /* draw game area */
- foreground(COL_DBACK);
- XFillRectangle(dpy,win,gc, drawx,drawy,draww,drawh);
- XSync(dpy,0);
-
- for (i=0; i<GRID_SIZE; i++) /* redraw grid */
- for (j=0; j<GRID_SIZE; j++)
- if (ptest(i,j)) /* if not BLANK */
- pset(i,j,grid[i][j]); /* draw it */
- }
-
- /*---------------------------------------------------------------------------*/
- /* look for the following events:-
- * window expos or size change - redraw it
- * window obscured - stop game
- * window visible - only start game if mouse present
- * mouse enters window - start game if window visible
- * mouse leaves window - stop game
- * keypress - store in last_key previous key has been read
- */
- static void check_events()
- {
- XEvent report;
- int expose = FALSE, config = FALSE;
- int temp1, temp2;
-
- while(XPending(dpy)) /* while events */
- {
- XNextEvent(dpy,&report); /* get event */
-
- switch(report.type)
- {
- case Expose: /* window exposed */
- expose = TRUE; /* flag redraw needed */
- break;
-
- case VisibilityNotify: /* visibility changed */
- if (report.xvisibility.state == VisibilityUnobscured)
- obscured = 0; /* say usable */
- /* - allow an Enter event to start prog */
- else
- {
- obscured = 1; /* say unusable */
- suspend = 1; /* and stop prog */
- }
- break;
-
- case ConfigureNotify: /* window size changed */
- winW = (int)(report.xconfigure.width); /* get new ones */
- winH = (int)(report.xconfigure.height);
- config = TRUE; /* flag redraw needed */
- break;
-
- case KeyPress: /* key hit */
- if (last_key_hit == 0) /* if last key was read - update it */
- last_key_hit = report.xkey.keycode;
- break;
-
- case EnterNotify: /* pointer in window */
- if (obscured == 0) /* if window usable */
- suspend = 0; /* restart prog */
- break;
-
- case LeaveNotify: /* pointer out of window */
- suspend = 1; /* stop prog */
- break;
-
- case DestroyNotify:
- printf("Window destroyed\n");
- exit(1);
- break;
-
- default:
- break;
- }
- }
- if (config) /* if window size may have changed */
- {
- choose_font(); /* select font to fit */
- set_scales(); /* rescale */
- }
-
- if (expose) /* simple expose */
- redraw_screen(); /* just redraw - do after config events */
- }
-
- /*---------------------------------------------------------------------------*/
- /* draw box at x,y on grid in colour col */
- static void draw_box_prim(x,y,col)
- int x,y,col;
- {
- int i, j;
-
- for (i=0; i<BOX_WIDTH; i++)
- for (j=0; j<BOX_HEIGHT; j++)
- pset(x+i, y+j, col);
- }
-
- /*---------------------------------------------------------------------------*/
- /* draw box at current x,y pos on grid in colour col */
- static void draw_box(col)
- int col;
- {
- draw_box_prim(box_x_pos, box_y_pos, col);
- }
-
- /*---------------------------------------------------------------------------*/
- /* choose a random position in the grid and if it's empty draw a box there in
- * the colour specified - returns TRUE if box created */
- static int new_box(col)
- int col;
- {
- int i, x,y, hit = FALSE;
-
- x = get_rnd(1,GRID_SIZE - BOX_WIDTH - 1); /* limit position to within */
- y = get_rnd(1,GRID_SIZE - BOX_HEIGHT - 1); /* grid bounds */
-
- for (i=0; i<BOX_WIDTH; i++) /* test x perimeter */
- if (ptest(x+i, y) || ptest(x+i, y+BOX_HEIGHT-1))
- hit = TRUE;
-
- if (!hit)
- { /* test y perimeter */
- for (i=0; i<BOX_HEIGHT; i++)
- if (ptest(x, y+i) || ptest(x+BOX_WIDTH-1, y+i))
- hit = TRUE;
- }
-
- if (!hit) /* no hit */
- {
- draw_box_prim(x,y, col); /* draw box */
- box_x_pos = x;
- box_y_pos = y; /* record new box position */
- }
-
- return (!hit);
- }
-
- /*---------------------------------------------------------------------------*/
- /* set some random boxes in the dead colour */
- static void set_random_boxes()
- {
- int count = 0;
-
- pset_inhibit = TRUE; /* disallow drawing - allow a redraw to do this */
-
- while (count < NUM_RAND_BOXES)
- {
- if (new_box(COL_BOX_DEAD)) /* if we drew one */
- count++; /* count it */
- }
- pset_inhibit = FALSE;
- }
-
- /*---------------------------------------------------------------------------*/
- /* game init.
- * draw board and set up all control variables */
- static void init_game()
- {
- int i,j;
-
- for (i=0; i<GRID_SIZE; i++) /* clear grid */
- for (j=0; j<GRID_SIZE; j++)
- grid[i][j] = COL_DBACK;
-
- for (i=0; i<MAX_TAIL; i++) /* clear ring */
- {
- ring[i][0] = 0;
- ring[i][1] = 0;
- }
-
- head_ring = 3; /* initial ring positions - initial len = 4 */
- tail_ring = 0;
-
- head_x = GRID_SIZE/2; /* head screen position */
- head_y = GRID_SIZE/2; /* dead centre */
-
- for (i=head_x; i<GRID_SIZE; i++) /* pretend space in front of snake */
- grid[i][head_y] = COL_SNAKE; /* is in use to prevent boxes */
- /* appearing there at prog start */
-
- set_random_boxes(); /* now set boxes */
-
- for (i=head_x; i<GRID_SIZE; i++) /* reset the space in front of snake */
- grid[i][head_y] = COL_DBACK;
-
- set_border(); /* now set border */
-
- /* place tail X and Y start coords into the ring */
- for (i=tail_ring; i<head_ring; i++)
- {
- ring[i][0] = head_x + i; /* X coord */
- ring[i][1] = head_y; /* Y coord */
- }
- /* NB. if we don't put tail into grid it emerges from a point at game start
- * which looks better ! */
-
- head_x += head_ring; /* correct X coord - to actual pos */
-
- xinc = 1; /* initial direction */
- yinc = 0;
-
- box_life = 0; /* no box present */
- box_delay = GRID_SIZE/10; /* delay before first box */
-
- tail_delay = 0; /* tail moves immediately */
- score = 0; /* no score */
- snake_len = head_ring - tail_ring; /* actual tail length */
-
- last_key_hit = 0; /* kill pending key hits */
- }
-
- /*---------------------------------------------------------------------------*/
- /* move head position and test for a hit.
- * if live box hit - remove box and adjust scores.
- * if hit anything else change game state and return FALSE
- * no hit - just draw new head pos. */
- static int move_head()
- {
- int retval = TRUE;
-
- head_x += xinc; /* move head */
- head_y += yinc;
-
- if (ptest(head_x, head_y)) /* has it hit */
- {
- /* is it the live box */
- if (head_x >= box_x_pos && head_x <= box_x_pos + BOX_WIDTH &&
- head_y >= box_y_pos && head_y <= box_y_pos + BOX_HEIGHT && box_life>0)
- {
- tail_delay = box_value; /* extend tail by new amount */
- snake_len += box_value; /* add to length */
- score += box_value; /* add to score */
- /* if box was dangerous */
- if (box_remain && box_life <= BOX_WARNING)
- score += 10; /* bonus points */
-
- box_life = 0; /* box is dead */
- draw_box(COL_DBACK); /* blank it */
-
- update_length(); /* display new length * score */
- update_score();
- }
- else /* not live box - you died */
- retval = FALSE; /* say dead */
- }
- if (retval) /* if not dead */
- {
- pset(head_x, head_y, COL_SNAKE); /* draw new head pos */
-
- ring[head_ring][0] = head_x; /* store new head pos in ring */
- ring[head_ring][1] = head_y;
-
- if (++head_ring > tail_max) /* wrap head to ring start */
- head_ring = 0;
- }
-
- return retval;
- }
-
- /*---------------------------------------------------------------------------*/
- /* move tail and erase old tail position */
- static void move_tail()
- {
- if (tail_delay >0) /* if tail growing - exit */
- {
- tail_delay--;
- return;
- }
- /* erase old position */
- pset(ring[tail_ring][0], ring[tail_ring][1], COL_DBACK);
-
- if (++tail_ring > tail_max) /* wrap tail to ring start */
- tail_ring = 0;
-
- if (tail_ring == head_ring) /* if tail hits head - delay it */
- tail_delay = 1; /* shouldn't happen - just incase */
- }
-
- /*---------------------------------------------------------------------------*/
- /* if box is dead and time for a new one draw it and set up box values.
- * if alive then see if its time for a warning or time to remove it */
- static void eval_box()
- {
- if (box_life > 0) /* if box alive */
- {
- --box_life;
- /* if will remain and warning due */
- if (box_life == BOX_WARNING && box_remain)
- draw_box(COL_BOX_WARN); /* show warning colour */
-
- if (box_life == 0) /* if box life ended */
- {
- if (box_remain) /* and will remain */
- draw_box(COL_BOX_DEAD); /* draw dead box */
- else
- {
- draw_box(COL_DBACK); /* wont remain - erase it */
- score -= 10; /* lose ten points */
- update_score(); /* show new score */
- }
- }
- }
- else /* box dead */
- {
- if (--box_delay <= 0) /* is it time for a new one */
- {
- if (new_box(COL_BOX_LIVE)) /* draw new box */
- {
- /* box life value allows you to move 1/2 to 1 and 1/4 across grid
- * before box dissapears */
- box_life = get_rnd(GRID_SIZE/2, GRID_SIZE+GRID_SIZE/4);
-
- /* 40% chance of box remaining */
- box_remain = (get_rnd(1,100) < 40) ? TRUE : FALSE;
-
- /* delay before new box allows you to move 1/4 to 3/4 across grid
- * before new box arrives */
- box_delay = get_rnd(GRID_SIZE/4,GRID_SIZE/2+GRID_SIZE/4);
-
- /* box value grows snake by 1/10 to 1/4 of grid size */
- box_value = get_rnd(GRID_SIZE/10,GRID_SIZE/4);
- }
- }
- }
- }
-
- /*---------------------------------------------------------------------------*/
- /* zero key store and return the key - if any */
- static int get_key()
- {
- int temp;
-
- temp = last_key_hit; /* get key */
- last_key_hit = 0; /* zero store */
-
- return temp;
- }
-
- /*---------------------------------------------------------------------------*/
- /* get key hit and adjust direction */
- static void direction_check()
- {
- int i, key, match = FALSE;
-
- if (key = get_key()) /* get key */
- {
- if (key == KEY_ESC) /* abort prog */
- {
- game_state = S_EXIT;
- return;
- }
-
- for (i=0; i<4; i++) /* for all directions */
- {
- if (key_list[i] == key) /* was it this key */
- {
- match = TRUE; /* yes */
- break;
- }
- }
- if (match) /* if we matched a key */
- {
- switch (i) /* depending on direction */
- { /* set increments */
- case KEY_DIR_UP:
- case KEY_DIR_DOWN:
- xinc = 0; /* no X movement */
- yinc = i == KEY_DIR_UP ? -1 : 1; /* set Y direction */
- break;
-
- case KEY_DIR_LEFT:
- case KEY_DIR_RIGHT:
- yinc = 0; /* no Y movement */
- xinc = i == KEY_DIR_LEFT ? -1 : 1; /* set X direction */
- break;
-
- default:
- break;
- }
- }
- }
- }
-
- /*---------------------------------------------------------------------------*/
- /* wait for key press and return it */
- static int wait_key()
- {
- int key;
-
- while((key = get_key()) == 0) /* wait for key hit */
- {
- nap(PAUSE);
- check_events();
- }
-
- return key;
- }
-
- /*---------------------------------------------------------------------------*/
- /* display usage message NB. don't document the -/ option */
- static void usage()
- {
- printf("Usage: xsnake [-lx] [-sx] [-h]\n\n");
-
- printf(
- "-l Set max tail length to x, where %d <= x <= %d (modulo %d).\n",
- MIN_TAIL, MAX_TAIL, INC_TAIL);
- printf("-s Set window scale to x, where 1 <= x <= %d.\n", NUM_FONTS);
- printf("-h Display help information.\n\n");
- exit(1);
- }
-
- /*---------------------------------------------------------------------------*/
- /* display the help text - with paging */
- static void help()
- {
- char **s1;
-
- s1 = help_text;
-
- while (*s1)
- printf("%s\n", *s1++);
-
- exit(1);
- }
-
- /*---------------------------------------------------------------------------*/
- /* prog init and game control */
- main(argc, argv)
- int argc;
- char **argv;
- {
- int key, i, suspend_noted = FALSE, stop, user_len = 0, user_size = 0;
- char s1[TEXT_COLUMNS];
-
- if (argc >1)
- {
- while (++argv, --argc) /* scan for args */
- {
- switch(argv[0][1])
- {
- case 'l': /* length change - mod it to INC_TAIL */
- user_len = atoi(&argv[0][2]);
- if (user_len < MIN_TAIL || user_len > MAX_TAIL)
- usage();
- user_len = user_len - user_len % 50;
- break;
- case 's': /* size change */
- user_size = atoi(&argv[0][2]);
- if (user_size <1 || user_size > NUM_FONTS)
- usage();
- break;
- case 'h': /* paged help - cheat by using */
- if (system("xsnake -/ | more") != 0) /* "snake -/" piped to more */
- help(); /* command failed - show it unpaged */
- exit(1);
- break;
- case '/': /* just output help text */
- help();
- break;
- default:
- usage();
- }
- }
- }
-
- find_file(); /* read keys, tail len, font and high score */
-
- if (user_len) /* user overrides */
- tail_max = user_len;
- if (user_size)
- current_font = user_size -1;
-
- if (user_len || user_size)
- file_save(); /* store user changes */
-
- pset_inhibit = FALSE; /* don't set points yet */
- redraw_skip = 0; /* allow redraw */
- game_state = S_INIT; /* initial state */
- suspend = TRUE; /* stop - until mouse enters first time */
- score_last = 0; /* no last score */
-
- create_window(); /* create window and set scaling */
- col_max_len = COL_FONT_NORM; /* set max len colour to normal - do after */
- /* colour load */
- set_scales(); /* set scales to initial window size */
- seed_rnd(); /* set random number generator */
- flush_events(); /* trash any pending events */
- /* we get lots at window create time */
-
- for(;;) /* forever */
- {
- switch (game_state) /* on game state */
- {
- case S_INIT: /* game init */
- banner(" "); /* clear banner line */
- init_game(); /* set up board etc */
- redraw_screen(); /* show screen */
- game_state = S_RUN; /* run it */
- break;
-
- case S_RUN: /* game running */
- if (!suspend) /* if not stopped */
- {
- if (suspend_noted) /* if banner on */
- {
- banner(" "); /* clear it */
- suspend_noted = FALSE;/* allow banner print */
- }
-
- if (move_head()) /* if havent died */
- {
- move_tail(); /* move tail - box and test keys */
- eval_box();
- direction_check();
- }
- else /* have died */
- {
- banner("* H I T - Press SPACE *");
- game_state = S_SPACE; /* change state */
- }
- } /* stopped */
- else
- {
- if (!suspend_noted) /* if banner not on */
- {
- banner("Game Paused");
- suspend_noted = TRUE; /* disallow banner print */
- }
-
- if (get_key() == KEY_ESC)/* flush keys and test for esc */
- game_state = S_EXIT; /* exit prog - even when paused */
- }
- break;
-
- case S_SPACE: /* waiting for space hit */
- get_key(); /* throw away any spurious spaces */
- if ((key = wait_key()) == KEY_ESC)
- game_state = S_EXIT; /* if ESC - exit */
- else
- if (key == KEY_SPACE)
- {
- if (score == SCORE_HIGH && score != 0)/* if same as high and */
- { /* worth a mention */
- banner("Matched High Score !");
- flash_banner();
- }
- if (score > SCORE_HIGH) /* if new high */
- {
- banner("New High Score !");
- flash_banner();
- SCORE_HIGH = score; /* update high - but don't display it yet */
- file_save(); /* save high score */
- }
- score_last = score; /* update last */
-
- game_state = S_CONT; /* if space - cont */
- banner("Any key for new game, CR to change config");
- }
- break;
-
- case S_CONT: /* waiting for continue or configure */
- get_key(); /* chuck spurious keys */
- if ((key = wait_key()) == KEY_ESC)
- game_state = S_EXIT; /* if esc - exit */
- else
- if (key == KEY_CR)
- {
- game_state = S_CONFIG;/* if cr - config */
- banner("Length, Key change or cancel (press L,K or Space)");
- }
- else
- game_state = S_INIT; /* other - then new game */
- break;
-
- case S_CONFIG: /* waiting for config type */
- get_key(); /* chuck spurious keys */
- if ((key = wait_key()) == KEY_L)
- game_state = S_SET_LEN; /* if L - then set length */
- else
- if (key == KEY_K) /* if K - then set keys */
- game_state = S_SET_KEY;
- else
- if (key == KEY_ESC) /* if esc - exit */
- game_state = S_EXIT;
- else
- game_state = S_INIT;/* other - abort config - new game */
- break;
-
- case S_SET_LEN: /* set length */
- stop = FALSE; /* don't exit yet */
- banner("Press < to decrease or > to increase, CR to set");
- col_max_len = COL_FONT_CHNG;/* highligh max length */
- update_max(); /* draw it in new colour */
- while(!stop)
- {
- key = get_key(); /* grab key */
- switch(key)
- {
- case KEY_LESS: /* if a < key - dec tail */
- tail_max -= INC_TAIL; /* limit to MIN_TAIL */
- if (tail_max < MIN_TAIL)
- tail_max = MIN_TAIL;
- update_max(); /* disp new value */
- update_high(); /* and associated high score */
- break;
- case KEY_MORE: /* if a > key - inc tail */
- tail_max += INC_TAIL; /* limit to MAX_TAIL */
- if (tail_max > MAX_TAIL)
- tail_max = MAX_TAIL;
- update_max(); /* disp new value */
- update_high(); /* and associated high score */
- break;
- case KEY_CR:
- col_max_len = COL_FONT_NORM;/* max len colour back to normal */
- file_save(); /* save new len */
- game_state = S_INIT; /* new game */
- stop = TRUE; /* exit loop */
- break;
- case KEY_ESC: /* exit prog */
- game_state = S_EXIT;
- stop = TRUE;
- break;
- default:
- break;
- }
- nap(PAUSE);
- check_events(); /* scan for keys etc */
- }
- break;
-
- case S_SET_KEY: /* key change */
- get_key(); /* chuck spurious keys */
- for (i=0; i<4; i++) /* for the four directions */
- {
- sprintf(s1, "Press the new %s key", key_dir[i]);
- banner(s1);
- key = wait_key();
-
- if (key == KEY_ESC) /* if esc - exit */
- {
- game_state = S_EXIT;
- break;
- }
- key_list[i] = key; /* store key press */
- }
- if (game_state != S_EXIT) /* if got keys ok then new game */
- {
- file_save(); /* save keys */
- game_state = S_INIT;
- }
- break;
-
- case S_EXIT: /* game exit */
- exit(0);
- break;
-
- default:
- printf("Game state error\n");
- exit(1);
- }
- check_events();
- nap(PAUSE);
- }
- }
- /* ends */
-